package jmine.tec.hydra.util.stream;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Representa um outputStream de bytes que pode ser transformado automaticamente em um inputStream para a leitura dos mesmo. Uma vez gerado
 * o inputStream, nenhum outro dado poderá ser gravado no outputStream que o gerou.
 * 
 * @author piercio
 */
public class InputableOutputStream extends OutputStream {

    private static final int MAX_BYTE_ARRAY_SIZE = 1048576;

    private OutputStream delegate;

    private boolean isDelegateChanged;

    private boolean isDone;

    private File tmpFile;

    private int size;

    /**
     * Construtor.
     */
    public InputableOutputStream() {
        this.delegate = new ByteArrayOutputStream();
        this.isDelegateChanged = false;
        this.isDone = false;
        this.size = 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() throws IOException {
        this.isDone = true;
        this.delegate.close();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void flush() throws IOException {
        this.delegate.flush();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        if (this.isDone) {
            throw new IOException("OutputStream already closed.");
        }

        if (!this.isDelegateChanged && this.size + len > MAX_BYTE_ARRAY_SIZE) {
            this.chengeDelegate();
        }

        this.delegate.write(b, off, len);
        this.size += len;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write(byte[] b) throws IOException {
        if (this.isDone) {
            throw new IOException("OutputStream already closed.");
        }

        if (!this.isDelegateChanged && this.size + b.length > MAX_BYTE_ARRAY_SIZE) {
            this.chengeDelegate();
        }

        this.delegate.write(b);
        this.size += b.length;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write(int b) throws IOException {
        if (this.isDone) {
            throw new IOException("OutputStream already closed.");
        }

        if (!this.isDelegateChanged && this.size > MAX_BYTE_ARRAY_SIZE) {
            this.chengeDelegate();
        }

        this.delegate.write(b);
        this.size++;
    }

    /**
     * Troca o OutputStream delegate para usar um arquivo quando o tamanho array de bytes ficar muito grande.
     * 
     * @throws IOException caso não consiga abrir/escrever no arquivo temporário.
     */
    private void chengeDelegate() throws IOException {
        this.tmpFile = File.createTempFile("hydra", "tmp");
        this.tmpFile.deleteOnExit();
        FileOutputStream newDelegate = new FileOutputStream(this.tmpFile);
        newDelegate.write(((ByteArrayOutputStream) this.delegate).toByteArray());

        this.isDelegateChanged = true;
        this.delegate = newDelegate;
    }

    /**
     * Devolve um InputStream com o conteúdo gravado nesse outputStream até o momento. Esse outputStream será fechado e não será mais
     * possível escrever nele.
     * 
     * @return um inputStream para a leitura dos bytes gravados até o momento.
     * @throws IOException caso não consiga criar o novo inputStream.
     */
    public InputStream getAsInputStream() throws IOException {
        this.delegate.close();
        this.isDone = true;
        if (this.isDelegateChanged) {
            return new FileInputStream(this.tmpFile);
        } else {
            return new ByteArrayInputStream(((ByteArrayOutputStream) this.delegate).toByteArray());
        }
    }

}
